package procmon;

BEGIN {
	push @INC,"/usr/local/cluster/lib/perl";
}

use XML::Simple;

sub new {
my $self={};
my $cfgfile=$_[1];
my ($cref,$cproc,$label,$xmlinfo,$procinfo);

	if(! -f "$cfgfile") {
		::logwarn("Specified configuration file $cfgfile does not exist - no monitoring!");
		return undef;
	}

	$xmlinfo=XMLin($cfgfile);
	if(! defined $xmlinfo) {
		#########################################################
		# The error message is available via the parent.	#
		#########################################################
		errmsg("Syntax error in $cfgfile",50);
	}

	#################################################################
	# Check the specified information exists, defaulting anything	#
	# possible.							#
	#################################################################

	if(!exists($xmlinfo->{global})) {
		::logwarn("Invalid format for $cfgfile - ignoring");
		return undef;
	}
	if(!exists($xmlinfo->{global}->{restarts}) || 
		!defined($xmlinfo->{global}->{restarts})) {
		::logwarn("global.restarts must be specified in $cfgfile - ignoring");
		return undef;
	}
	if(!exists($xmlinfo->{global}->{restartcmd}) || 
		!defined($xmlinfo->{global}->{restartcmd})) {
		::logwarn("global.restartcmd must be specified in $cfgfile - ignoring");
		return undef;
	}
	my $xx=$xmlinfo->{global}->{restartcmd}; $xx =~ s/^[\s+\r\n]+//;
	$cmd=(split(/[\s;]+/,$xx))[0];
	if(! -f $cmd) {
		::logwarn("Can not find specified restart command ($cmd) - ignoring");
		return undef;
	}

	if(!exists($xmlinfo->{global}->{logdir}) || 
		!defined($xmlinfo->{global}->{logdir})) {
		::logwarn("global.logdir defaulted to /var/log/cluster");
		$xmlinfo->{global}->{logdir}="/var/log/cluster";
	}
	if(!exists($xmlinfo->{global}->{resetwindow}) || 
		!defined($xmlinfo->{global}->{resetwindow})) {
		::logwarn("global.resetwindow defaulted to 3600");
		$xmlinfo->{global}->{resetwindow}=3600;
	}

	#################################################################
	# Now we check the process entries - might be one or more. 	#
	# If one then elements will be process->user, whilst if there	#
	# are more than one will be process->[0]->user...		#
	#################################################################

	$procinfo={};
	if(ref($xmlinfo->{process}) eq "HASH") {
		#########################################################
		# Parse one element.					#
		#########################################################
		($cproc,$label)=check_it($xmlinfo->{process});
		$procinfo->{$label}=$cproc if defined($cproc);
	} else {
		#########################################################
		# Parse array of elements.				#
		#########################################################
		foreach $cref (@{$xmlinfo->{process}}) {
			($cproc,$label)=check_it($cref);
			$procinfo->{$label}=$cproc if defined($cproc);
		}
	}
	$self->{PROCS}=$procinfo;
	$self->{XMLINFO}=$xmlinfo;

	#################################################################
	# We keep the details of the current flags in a reference to	#
	# a hash - obviously empty until the first check.		#
	#################################################################

	$self->{LASTRESTART}=0;
	$self->{RESTART_COUNT}=0;

	bless($self);
	return $self;
}

#########################################################################
# The accept method is optional, and is used to define additional	#
# messages that can be passed to modules. For the procmon module it	#
# expects the string to be one of the following:			#
# "RESET" 	- reset the current usage counter.			#
# "MAXCOUNT n"	- dynamically change the maximum number of resets	#
#########################################################################

sub accept {
my $self=shift;
my $msg=shift;
my (@f);

	return "INVALID_CMD" if !defined($msg);
	@f=split(/\s+/,$msg);
	return "INVALID_CMD" if $f[0] eq "";

	if($f[0] eq "RESET") {
		$self->{RESTART_COUNT}=0;
		$self->{LASTRESTART}=0;
		::logmsg("Reset restart count to 0.");
		return "OK";
	}
	if($f[0] eq "MAXCOUNT") {
		::logmsg("f1=$f[1]");
		if(!defined($f[1]) || $f[1] eq "" || $f[1]<1) {
			return "INVALID_ARG";
		}

		$self->{XMLINFO}->{global}->{restarts}=$f[1];
		::logmsg("Set maximum restart count to $f[1]");
		return "OK";
	}
	return "INVALID_CMD";
}

#########################################################################
# The check_entry routine will take a search user and pattern and	#
# will return the number of matching processes.				#
#########################################################################

sub check_entry {
my ($s_user,$s_pattern)=@_;
my ($fd,$cline,$had,@F,$pid,$user,$cmd);

	if(!open($fd,"ps -eo 'pid,user,cmd'|")) {
		::logwarn("Unable to read process table for monitor!");
		return -1;
	}
	$had=0;
	while(<$fd>) {
		chomp;
		@F=split;
		$user=$F[1];
		$cmd=join(" ",@F[2..$#F]);
		$pid=$F[0];
		if($user =~ /$s_user/ && $cmd =~ /$s_pattern/) {
			$had++;
		}
	}
	close($fd);
	return $had;
}

sub check {
my $self=shift;
my ($had,$cref,$clabel,$ok,$cmd);

	$ok=1;
	foreach $clabel (keys %{$self->{PROCS}}) {
		$cref=$self->{PROCS}->{$clabel};
		$had=check_entry($cref->{user},$cref->{process});
		if($had==-1) {
			::logwarn("Unable to read process table for $label!");
			next;
		}
		if($had<$cref->{min} || $had>$cref->{max}) {
			$ok=0;
			if($had<$cref->{min}) {
				::logwarn("The number of processes that matched user:pattern '$cref->{user}:$cref->{process}'");
				::logwarn("was $had - this is less than the required minimum of $cref->{min} indicating");
				::logwarn("the application is not running correctly and should be restarted.");
			} else {
				::logwarn("The number of processes that matched user:pattern '$cref->{user}:$cref->{process}'");
				::logwarn("was $had - this is more than the configured maximum of $cref->{max} indicating");
				::logwarn("the application is not running correctly and should be restarted.");
			}
			last;
		}
	}
	if($ok==1) {
		# See if we can reset the restart count ourselves.	#
		if($self->{RESTART_COUNT}>0 &&
			$self->{LASTRESTART}+$self->{XMLINFO}->{global}->{resetwindow} < time) {
				$self->{RESTART_COUNT}=0;
				::logmsg("Restart count reset to 0");
		}
		return 0;
	}

	#################################################################
	# If we get here something needs to be done - either restarting	#
	# the application, or even attempting a fail-over!		#
	#################################################################
	$self->{RESTART_COUNT}++;
	if($self->{RESTART_COUNT} >= $self->{XMLINFO}->{global}->{restarts}) {
		#########################################################
		# Yikes - failover!					#
		#########################################################
		::logmsg("Monitor process has been restarted too often - stopping application on this node!");
		return 2;
	}
	#################################################################
	# Set the last restart time, and restart the process.		#
	#################################################################
	$self->{LASTRESTART}=time;
	$cmd=$self->{XMLINFO}->{global}->{restartcmd};
	::logmsg("Restarting the application ($cmd) ...");
	`$cmd`;
	return 0;
}

#########################################################################
# Checks the process_string,min_count,max_count,user and label setting	#
# of the specified reference and returns a reference to the info and	#
# a label if the correct fields are defined.				#
#########################################################################

sub check_it {
my $ref=shift;
my ($nref,$label);

	$nref={};
	$label="";
	if(!exists($ref->{label}) || !defined($ref->{label})) {
		::logwarn("Entry has missing or undefined label - ignoring");
		return (undef,undef);
	}
	if(!exists($ref->{process_string}) || !defined($ref->{process_string})) {
		::logwarn("Entry \"$label\" has missing or undefined process_string - ignoring");
		return (undef,undef);
	}
	$nref->{label}=$label;
	$nref->{process}=$ref->{process_string};
	$nref->{process} =~ s/^[\s+\r\n]+//;
	$nref->{process} =~ s/[\s+\r\n]+$//;
	if(exists($ref->{min_count}) && defined($ref->{min_count})) {
		$nref->{min}=$ref->{min_count};
	} else {
		$nref->{min}=1;
		::logmsg("Defaulting min count for \"$label\" to 1.");
	}
	if(exists($ref->{max_count}) && defined($ref->{max_count})) {
		$nref->{max}=$ref->{max_count};
	} else {
		$nref->{max}=999;
		::logmsg("Defaulting max count for \"$label\" to 999.");
	}
	if(exists($ref->{user}) && defined($ref->{user})) {
		$nref->{user}=$ref->{user};
	} else {
		$nref->{user}=".*";
		::logmsg("Defaulting user match for \"$label\" to \".*\"");
	}

	return ($nref,$label);
}

sub stat {
my $self=shift;
my ($str,$t);

	#################################################################
	# The information we return here is:				#
	# PROCMON:current restarts, max restarts,lastrestart, whenreset	#
	# example:							#
	# PROCMON:2,5,312313121,200					#
	#################################################################

	$str="PROCMON:";
	$str.=$self->{RESTART_COUNT}.",";
	$str.=$self->{XMLINFO}->{global}->{restarts}.",";
	$str.=$self->{LASTRESTART}.",";
	if($self->{LASTRESTART}==0) {
		$t=-1;
	} else {
		$t=$self->{LASTRESTART}+$self->{XMLINFO}->{global}->{resetwindow};
	}
	$str.="$t\n";
	return $str;
}
1;
